package com.googlecode.ddlutilstool.gui;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.beans.PropertyVetoException;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;

import org.apache.log4j.Logger;
import org.apache.log4j.Priority;

import com.googlecode.ddlutilstool.common.GuiNameAccessor;
import com.googlecode.ddlutilstool.gui.EditorPanel.DatabaseFileFilter;

/**
 * <p>This panel contains the editor windows.
 * It uses Swing's JDesktopPane to support the
 * multiple MDI style interface.</p>
 */
@SuppressWarnings("serial")
public class EditorDesktop extends JDesktopPane implements InternalFrameListener {

	private static final Logger mLog = Logger.getLogger(EditorDesktop.class);

	protected GuiNameAccessor mGuiAccessor;
	protected MainFrame mFrame;
	protected ArrayList<EditorPanel> mEditorPanels;
	
    protected UndoAction undoAction;
    protected RedoAction redoAction;
    protected CopyAction copyAction;
    protected CutAction cutAction;
    protected SaveAction saveAction;
    protected SaveAsAction saveAsAction;
    protected OpenAction openAction;
    protected CloseAction closeAction;
    protected PasteAction pasteAction;
    protected NewAction newAction;
    
    public EditorDesktop() {
		super();
		mGuiAccessor = new GuiNameAccessor();
		mEditorPanels = new ArrayList<EditorPanel>();

		undoAction = new UndoAction();
	    redoAction = new RedoAction();
	    copyAction = new CopyAction();
	    cutAction = new CutAction();
	    pasteAction = new PasteAction();
	    saveAction = new SaveAction();
	    saveAsAction = new SaveAsAction();
	    openAction = new OpenAction();
	    closeAction = new CloseAction();
	    newAction = new NewAction();
	    
		//setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
	}
    
    /**
     * @return a list of the existing editor panels.
     */
    public ArrayList<EditorPanel> getEditorPanels() {
    	return mEditorPanels;
    }
    
    /**
     * Passes in the parent frame, just for reference.
     * @param pFrame
     */
    public void setFrame(MainFrame pFrame) {
    	mFrame = pFrame;
    }
    
	/**
	 * Part of the InternalFrameListener interface.  It updates
	 * the menu with options from the activated window.  This
	 * will impact the edit and file menu that depend on the 
	 * content of the window.
	 */
	public void internalFrameActivated(InternalFrameEvent e) {
		updateActions();
	}

	/**
	 * Part of the InternalFrameListener interface. 
	 * Does nothing here.
	 */
	public void internalFrameClosed(InternalFrameEvent e) {
	}

	/**
	 * Part of the InternalFrameListener interface. 
	 * Does nothing here.
	 */
	public void internalFrameClosing(InternalFrameEvent e) {
	}

	/**
	 * Part of the InternalFrameListener interface. 
	 * Does nothing here.
	 */
	public void internalFrameDeactivated(InternalFrameEvent e) {
	}

	/**
	 * Part of the InternalFrameListener interface. 
	 * Does nothing here.
	 */
	public void internalFrameDeiconified(InternalFrameEvent e) {
	}

	/**
	 * Part of the InternalFrameListener interface. 
	 * Does nothing here.
	 */
	public void internalFrameIconified(InternalFrameEvent e) {
	}

	/**
	 * Part of the InternalFrameListener interface. 
	 * Sets the newly openened window as the active
	 * window, bringing it to the front and updating
	 * the menu options.
	 */
	public void internalFrameOpened(InternalFrameEvent e) {
		setFrameActive(e.getInternalFrame());
	}

	/**
	 * Forces the current window to be the active window,
	 * displaying it up front, and updating the menus.
	 * 
	 * @param pFrame the frame to be active
	 */
	protected void setFrameActive(JInternalFrame pFrame) {
		this.setSelectedFrame(pFrame);
		try {
			pFrame.setSelected(true);
		}
		catch (PropertyVetoException e) {
			if (mLog.isEnabledFor(Priority.ERROR)) mLog.error(e);
		}
		pFrame.moveToFront();
	}
	
	/**
	 * Gets the currently active editor.
	 * @return
	 */
	protected EditorPanel getSelectedEditor() {
		JInternalFrame pane = super.getSelectedFrame();
		if (pane!=null && pane instanceof EditorInternalFrame) {
			return ((EditorInternalFrame)pane).mPanel;
		}
		return null;
	}

	/**
	 * Checks through all windows to see if any are dirty.
	 * This is used when trying to close the application
	 * to see if there is any unsaved data.
	 * 
	 * @return
	 */
	public boolean isAnyEditorDirty() {
		JInternalFrame[] frames = getAllFrames();
		if (frames!=null) {
			for (JInternalFrame frame : frames) {
				if (frame!=null && frame instanceof EditorInternalFrame) {
					if (((EditorInternalFrame)frame).getEditorPanel().isDirty()) {
						return true;
					}
				}
			}
		}
		return false;
	}
	
	/**
	 * Checks if only the active window is dirty
	 * and needs saving.  This is used when about
	 * to execute DDL code that may use the contents
	 * of the active window.
	 * 
	 * @return
	 */
	public boolean isCurrentEditorDirty() {
		EditorPanel panel = getSelectedEditor();
		if (panel!=null) {
			return panel.isDirty();
		}
		return false;
	}

	/**
	 * Checks if the current window has no data.
	 * This is used when about
	 * to execute DDL code that may use the contents
	 * of the active window.
	 * 
	 * @return
	 */
	public boolean isCurrentEditorEmpty() {
		EditorPanel panel = getSelectedEditor();
		if (panel!=null) {
			return panel.isEmpty();
		}
		return true;
	}

	/**
	 * Gets the contents of the current window
	 * as a Reader.  This is used when about
	 * to execute DDL code that may use the contents
	 * of the active window.
	 * 
	 * @return
	 */
	public Reader getCurrentEditorReader() {
		EditorPanel panel = getSelectedEditor();
		if (panel!=null) {
			return panel.getReader();
		}
		return null;
	}
	
	/**
	 * Called by the New menu option, it creates a new window.
	 */
	public void fileNew() {
		EditorInternalFrame frame = new EditorInternalFrame(this, mEditorPanels.size());
		EditorPanel panel = frame.getEditorPanel();
		panel.setTitle("Untitled");
		frame.setVisible(true);
		super.add(frame);
		mEditorPanels.add(panel);
		setFrameActive(frame);
		updateActions();
	}

	/**
	 * Called after executing DDL, it will create a new window
	 * with the given filename and contents.  This will contain
	 * the DDL output.
	 * 
	 * @param pContent
	 * @param pFilename
	 */
	public void fileNew(String pContent, String pFilename) {
		EditorInternalFrame frame = new EditorInternalFrame(this, mEditorPanels.size());
		EditorPanel panel = frame.getEditorPanel();
		panel.forceText(pContent, pFilename);
		frame.setVisible(true);
		super.add(frame);
		mEditorPanels.add(panel);
		setFrameActive(frame);
		updateActions();
		mFrame.updateWindowsMenu();
	}

	/**
	 * Called after the user opens a new file, it will
	 * create the window and fill it with the contents
	 * of the file.
	 * 
	 * @param pFile the selected file to open
	 * @throws IOException the file could not be opened
	 */
	public void fileNew(File pFile) throws IOException {
		EditorInternalFrame frame = new EditorInternalFrame(this, mEditorPanels.size());
		EditorPanel panel = frame.getEditorPanel();
		panel.load(pFile);
		frame.setVisible(true);
		super.add(frame);
		mEditorPanels.add(panel);
		setFrameActive(frame);
		updateActions();
		mFrame.updateWindowsMenu();
	}
	
	/**
	 * Called by either the Save or Save As menu
	 * options, or when a user selects to save the
	 * current window's content from a dialog,
	 * it will ask the user for the filename
	 * using the JFileChooser, then save the current
	 * window's content as that file.
	 * @return true if it saved the file
	 */
	public boolean fileSaveAs() {
		EditorPanel panel = getSelectedEditor();
		if (panel!=null) {
			File dir = new File(".");
			if (panel.getFile()!=null) {
				dir = panel.getFile().getParentFile();
			}
			JFileChooser fc = new JFileChooser(dir);
			fc.setFileFilter(new DatabaseFileFilter());
			if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
				try {
					if (panel.saveInternal(fc.getSelectedFile())) {
						mFrame.updateWindowsMenu();
						return true;
					}
					else {
						return false;
					}
				}
				catch (IOException e) {
					error(mGuiAccessor.getString("editor.save.error",new String[] {e.getMessage()}));
					return false;
				}
			}
			else {
				return false;
			}
		}
		else {
			error(mGuiAccessor.getString("editor.no.editor.selected"));
			return false;
		}
	}
	
	/**
	 * Called by the Save menu option, or when a user selects to 
	 * save the current window's content from a dialog,
	 * it will save the current
	 * window's content as that file.
	 * @return true if it saved the file
	 */
	public boolean fileSave() {
		EditorPanel panel = getSelectedEditor();
		if (panel!=null) {
			if (panel.isDirty() && panel.getFile()==null) {
				return fileSaveAs();
			}
			else if (panel.isDirty() && panel.getFile()!=null) {
				try {
					return panel.saveInternal();
				}
				catch (IOException e) {
					error(mGuiAccessor.getString("editor.save.error",new String[] {e.getMessage()}));
					return false;
				}
			}
			else {
				return true;
			}
		}
		else {
			error(mGuiAccessor.getString("editor.no.editor.selected"));
			return false;
		}
	}
	

	/**
	 * Called by the Open menu option, 
	 * it will display the JFileChooser and
	 * let the caller select a file to open.
	 * If they select one, it calls fileNew()
	 * with the selected file, opening the file
	 * into a new window.
	 */
	public void fileOpen() {
		File dir = new File(".");
		JFileChooser fc = new JFileChooser(dir);
		fc.setFileFilter(new DatabaseFileFilter());
		if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
			try {
				fileNew(fc.getSelectedFile());
			}
			catch (IOException e) {
				error(mGuiAccessor.getString("editor.open.error",new String[] {e.getMessage()}));
			}
			mFrame.updateWindowsMenu();
		}
	}

	/**
	 * Called from the Close menu, it will close the current
	 * selected window.  If the window is dirty, it will first
	 * ask the user if they want to save the file, and save
	 * it if they do.
	 */
	public void fileClose() {
    	JInternalFrame pane = getSelectedFrame();
		if (pane!=null && pane instanceof EditorInternalFrame) {
			EditorInternalFrame editorPane = ((EditorInternalFrame)pane);
			EditorPanel panel = editorPane.mPanel;

			if (panel.isDirty()) {
				int answer = question(mGuiAccessor.getString("editor.dirty.question"));
				if (answer!=JOptionPane.YES_OPTION) {
					return;
				}
			}
    		panel.forceClose();
			try {
				pane.setClosed(true);
				mEditorPanels.remove(panel);
				updateActions();
			}
			catch (PropertyVetoException ex) {
				if (mLog.isEnabledFor(Priority.ERROR)) mLog.error(ex);
			}
			mFrame.updateWindowsMenu();
    	}
	}

	/**
	 * Displays the error message in a dialog.
	 * 
	 * @param pErrorMessage
	 */
	protected void error(String pErrorMessage) {
		JOptionPane.showMessageDialog(this,
			pErrorMessage,
			mGuiAccessor.getString("label.application"),
			JOptionPane.ERROR_MESSAGE);

	}
	
	/**
	 * Displays the question in a dialog.  The response
	 * is a constant in the JOptionPane class.
	 * 
	 * @param pMessage
	 * @return
	 */
	protected int question(String pMessage) {
		return JOptionPane.showConfirmDialog(this,
			pMessage,
			mGuiAccessor.getString("label.application"),
			JOptionPane.INFORMATION_MESSAGE);
	}
	
	/**
	 * @return the Edit | Undo action
	 */
	public UndoAction getUndoAction() {
		return undoAction;
	}

	/**
	 * @return the Edit | Redo action
	 */
	public RedoAction getRedoAction() {
		return redoAction;
	}

	/**
	 * @return the Edit | Copy action
	 */
	public CopyAction getCopyAction() {
		return copyAction;
	}

	/**
	 * @return the Edit | Cut action
	 */
	public CutAction getCutAction() {
		return cutAction;
	}

	/**
	 * @return the Edit | Paste action
	 */
	public PasteAction getPasteAction() {
		return pasteAction;
	}

	/**
	 * @return the File | Save action
	 */
	public SaveAction getSaveAction() {
		return saveAction;
	}
	
	/**
	 * @return the File | Save As action
	 */
	public SaveAsAction getSaveAsAction() {
		return saveAsAction;
	}
	
	/**
	 * @return the File | Open action
	 */
	public OpenAction getOpenAction() {
		return openAction;
	}
	
	/**
	 * @return the File | Close action
	 */
	public CloseAction getCloseAction() {
		return closeAction;
	}
	
	/**
	 * @return the File | New action
	 */
	public NewAction getNewAction() {
		return newAction;
	}
	
	/**
	 * <p>Called when there was change to the window,
	 * it will make any required changes to the
	 * following menu options:</p>
	 * 
	 * <ul>
	 * <li>Undo</li>
	 * <li>Redo</li>
	 * <li>Copy</li>
	 * <li>Cut</li>
	 * <li>Paste</li>
	 * <li>Save</li>
	 * <li>Save As</li>
	 * <li>Open</li>
	 * <li>Close</li>
	 */
	void updateActions() {
		EditorPanel panel = getSelectedEditor();
		if (panel!=null) {
			
			//update undo
            if (panel.canUndo()) {
            	undoAction.setEnabled(true);
                undoAction.putValue(Action.NAME, panel.getUndoPresentationName());
            } else {
            	undoAction.setEnabled(false);
            	undoAction.putValue(Action.NAME, "Undo");
            }
            
            //update redo
            if (panel.canRedo()) {
                redoAction.setEnabled(true);
                redoAction.putValue(Action.NAME, panel.getRedoPresentationName());
            } else {
            	redoAction.setEnabled(false);
            	redoAction.putValue(Action.NAME, "Redo");
            }
            
            //update copy/cut/paste
            copyAction.setEnabled(true);
            cutAction.setEnabled(true);
            pasteAction.setEnabled(true);
            
            //save, open, saveas, close
            saveAction.setEnabled(panel.isDirty());
            saveAsAction.setEnabled(true);
            openAction.setEnabled(true);
            closeAction.setEnabled(true);
		}
	}

	/**
	 * This class represents each individual editor
	 * window.  Each window contains an EditorPanel.
	 */
	class EditorInternalFrame extends JInternalFrame {
		EditorPanel mPanel;
		
		public EditorInternalFrame(EditorDesktop pParent,int pCount) {
			super("",true,false,true,true);
			mPanel = new EditorPanel(pParent,this);
			setSize(300,300);
			setLocation(30*pCount, 30*pCount);
			getContentPane().setLayout(new BorderLayout());
			getContentPane().add(mPanel,BorderLayout.CENTER);
			addInternalFrameListener(pParent);
		}
		
		public EditorPanel getEditorPanel() {
			return mPanel;
		}
	}
	
	/**
	 * The Undo action uses the currently active
	 * window's UndoManager to manage and execute
	 * undoing.
	 */
	class UndoAction extends AbstractAction {
        public UndoAction() {
            super("Undo",mGuiAccessor.getImageIcon("menu.edit.undo.button"));
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
        	EditorPanel panel =  getSelectedEditor();
        	if (panel!=null) {
	        	if (panel.canUndo()) {
	        		try {
		                panel.undo();
		            } 
		            catch (CannotUndoException ex) {
		                System.out.println("Unable to undo: " + ex);
		                ex.printStackTrace();
		            }
		            updateActions();
		            panel.updateDirty();
	        	}
        	}
        }

    }

	/**
	 * The Redo action uses the currently active
	 * window's UndoManager to manage and execute
	 * redoing.
	 */
    class RedoAction extends AbstractAction {
        public RedoAction() {
            super("Redo",mGuiAccessor.getImageIcon("menu.edit.redo.button"));
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
        	EditorPanel panel =  getSelectedEditor();
        	if (panel!=null) {
 	        	if (panel.canRedo()) {
		            try {
		            	panel.redo();
		            } catch (CannotRedoException ex) {
		                System.out.println("Unable to redo: " + ex);
		                ex.printStackTrace();
		            }
		            updateActions();
		            panel.updateDirty();
	        	}
        	}
        }

    }

    /**
     * The Copy Action calls copy() on the currently
     * active window.
     */
    class CopyAction extends AbstractAction {
        public CopyAction() {
            super(mGuiAccessor.getString("menu.edit.copy"),mGuiAccessor.getImageIcon("menu.edit.copy.button"));
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
        	EditorPanel panel =  getSelectedEditor();
        	if (panel!=null) {
        		panel.copy();
        	}
        }
    }

    /**
     * The Cut Action calls cut() on the currently
     * active window.
     */
    class CutAction extends AbstractAction {
        public CutAction() {
            super(mGuiAccessor.getString("menu.edit.cut"),mGuiAccessor.getImageIcon("menu.edit.cut.button"));
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
        	EditorPanel panel =  getSelectedEditor();
        	if (panel!=null) {
        		panel.cut();
        	}
        }
    }

    /**
     * The Paste Action calls paste() on the currently
     * active window.
     */
    class PasteAction extends AbstractAction {
        public PasteAction() {
            super(mGuiAccessor.getString("menu.edit.paste"),mGuiAccessor.getImageIcon("menu.edit.paste.button"));
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
        	EditorPanel panel =  getSelectedEditor();
        	if (panel!=null) {
        		panel.paste();
        	}
        }
    }

    /**
     * The Save Action calls fileSave(), saving
     * the active window.
     */
    class SaveAction extends AbstractAction {
        public SaveAction() {
            super(mGuiAccessor.getString("menu.file.save"),mGuiAccessor.getImageIcon("menu.file.save.button"));
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
        	fileSave();
        }
    }

    /**
     * The Save As Action calls fileSaveAs(), saving
     * the active window.
     */
    class SaveAsAction extends AbstractAction {
        public SaveAsAction() {
            super(mGuiAccessor.getString("menu.file.saveas"),mGuiAccessor.getImageIcon("menu.file.saveas.button"));
            setEnabled(true);
        }

        public void actionPerformed(ActionEvent e) {
        	fileSaveAs();
        }
    }

    /**
     * The Open Action calls fileOpen(), allowing
     * the user to open a new file into a new
     * window.
     */
    class OpenAction extends AbstractAction {
        public OpenAction() {
            super(mGuiAccessor.getString("menu.file.open"),mGuiAccessor.getImageIcon("menu.file.open.button"));
            setEnabled(true);
        }

        public void actionPerformed(ActionEvent e) {
        	fileOpen();
        }
    }

    /**
     * The Close Action calls fileClose(), closing
     * the currently active window.     
     */
    class CloseAction extends AbstractAction {
        public CloseAction() {
            super(mGuiAccessor.getString("menu.file.close"),mGuiAccessor.getImageIcon("menu.file.close.button"));
            setEnabled(true);
        }

        public void actionPerformed(ActionEvent e) {
        	fileClose();
        }
    }

    /**
     * The New Action calls fileNew(), creating
     * a new blank window.
     */
    class NewAction extends AbstractAction {
        public NewAction() {
            super(mGuiAccessor.getString("menu.file.new"),mGuiAccessor.getImageIcon("menu.file.new.button"));
            setEnabled(true);
        }

        public void actionPerformed(ActionEvent e) {
        	fileNew();
        }
    }

}
